<?php
namespace App\Core;


use App\DataBase\Factory;
use App\Exception\DispatcherException;
use App\Exception\InterceptorException;

class Dispatcher
{
    protected $_router;
    protected $_app;

    public function __construct(App $app)
    {
        $this->_router = new Router($app);
        $this->_app = $app;
    }

    /**
     * 框架真正的主方法。依次执行各个拦截器的before方法、控制器方法、拦截器逆序after方法，
     * 以及页面方法。最后做清理工作。
     */
    public function dispatch()
    {
        $controllerName = $this->_router->route();

        $controller = $this->getControllerInstance($controllerName);
        $interceptorClasses = $this->getInterceptorClasses($controllerName);

        $interceptors = [];
        $step = Interceptor::STEP_CONTINUE;
        foreach ($interceptorClasses as $interceptorClass) {
            $interceptor = $this->getInterceptorInstance($interceptorClass);

            $interceptors[] = $interceptor;
            $step = $interceptor->before();
            if ($step != Interceptor::STEP_CONTINUE) {
                break;
            }
        }

        /**
         * APF 基本逻辑：
         *
         * 控制器执行完毕后返回的结果存 $result，处理以下 4 种情况：
         *
         * 1. $result 是 Controller，继续执行这个 Controller
         *
         * 尽早关闭 PDO，我们认为 Page 部分不需要 PDO 了。
         * Interceptor 的 after 部分也不应该再使用 PDO 。
         *
         * 2. $result 为新的形式 (string, params KV)
         * 3. $result 为传统形式 (stirng) 不推荐使用
         * 4. $result 为空
         */
        if ($step != Interceptor::STEP_EXIT) {
            $result = $controller->handle_request();

            /** Controller 之后不再允许访问数据库 */
            if (class_exists('APF_DB_Factory', false)) {
                Factory::getInstance()->closeAll();
            }

            if (is_array($result)) {
                list($viewClass, $params) = $result;
                $this->render($viewClass, $params);
            } else if (is_string($result)) {
                $this->render($result);
            }
        }

        if ($interceptors) {
            $interceptors = array_reverse($interceptors);
            foreach ($interceptors as $interceptor) {
                /**
                 * @var Interceptor $interceptor
                 */
                $step = $interceptor->after();
                if ($step != Interceptor::STEP_CONTINUE) {
                    break;
                }
            }
        }
    }

    /**
     * 获取控制器类实例
     *
     * @param string $controller
     * @return Controller
     * @throws DispatcherException
     */
    public function getControllerInstance($controller)
    {
        $controllerClass = $controller . 'Controller';
        if (!class_exists($controllerClass, true)) {
            throw new DispatcherException();
        }
        return new $controllerClass();
    }

    /**
     * 获取拦截器对象实例
     *
     * @param string $interceptor
     * @return Interceptor
     * @throws InterceptorException
     */
    public function getInterceptorInstance($interceptor)
    {
        $interceptorClass = $interceptor . 'Interceptor';
        if (!class_exists($interceptorClass, true)) {
            throw new InterceptorException();
        }
        return new $interceptorClass($this);
    }

    /**
     * 获取拦截器
     *
     * @param string $controller
     * @return array
     */
    public function getInterceptorClasses($controller)
    {
        $finalInterceptorClasses = [];

        $globalInterceptorClasses = $this->_app->getConfig('global', 'interceptor');
        if ($globalInterceptorClasses && !is_array($globalInterceptorClasses)) {
            $globalInterceptorClasses = [$globalInterceptorClasses];
        }
        $interceptorClasses = $this->_app->getConfig($controller, 'interceptor');
        if (!$interceptorClasses) {
            $interceptorClasses = $this->_app->getConfig('default', 'interceptor');
        }
        if ($interceptorClasses && !is_array($interceptorClasses)) {
            $interceptorClasses = [$interceptorClasses];
        }

        foreach ($globalInterceptorClasses as $value) {
            $finalInterceptorClasses[$value] = $value;
        }
        foreach ($interceptorClasses as $value) {
            if (preg_match('#^!#', $value)) {
                $value = substr($value, 1);
                unset($finalInterceptorClasses[$value]);
            } else {
                $finalInterceptorClasses[$value] = $value;
            }
        }
        return $finalInterceptorClasses;
    }

    public function render($viewClass, $params = [])
    {
        /**
         * @var View $viewInstance
         */
        $viewInstance = new $viewClass();
        $viewInstance->setParams($params);
        $viewInstance->execute();
    }
}